package com.qkun.library.base.binding.service;

import android.annotation.SuppressLint;
import android.content.Intent;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.collection.ArrayMap;

import com.qkun.library.base.BaseService;
import com.qkun.library.entry.RemoteUserInfo;
import com.qkun.library.utils.MyLog;
import com.qkun.library.utils.RunHandler;
import com.qkun.library.utils.Utils;

import java.util.Collection;

import static com.qkun.library.base.binding.service.BindingProtocol.CLIENT_MSG_CONNECT;
import static com.qkun.library.base.binding.service.BindingProtocol.CLIENT_MSG_DISCONNECT;
import static com.qkun.library.base.binding.service.BindingProtocol.DATA_CALLING_PID;
import static com.qkun.library.base.binding.service.BindingProtocol.DATA_CALLING_UID;
import static com.qkun.library.base.binding.service.BindingProtocol.DATA_PACKAGE_NAME;
import static com.qkun.library.base.binding.service.BindingProtocol.SERVICE_MSG_ON_CONNECT;
import static com.qkun.library.base.binding.service.BindingProtocol.SERVICE_VERSION_CURRENT;

public abstract class BindingService extends BaseService {

    public static final String SERVICE_INTERFACE = "com.base.BindingService";

    private final ArrayMap<IBinder, ConnectionRecord> mConnectionRecords = new ArrayMap<>();
    private final ServiceHandler mServiceHandler = new ServiceHandler();
    private final Messenger mServiceMessenger = new Messenger(mServiceHandler);
    private final BindingToken mToken = new BindingToken(this);

    public static Intent getBindingIntent() {
        return new Intent(SERVICE_INTERFACE);
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        if (SERVICE_INTERFACE.equals(intent.getAction())) {
            return mServiceMessenger.getBinder();
        }
        return super.onBind(intent);
    }

    // TODO: 10/14 0014 判断字符串不为null并且不为“”的注解
    @NonNull
    protected ConnectionRecord onSetupConnectionRecord(@NonNull String pkg, int pid, int uid, @NonNull CallbacksWrapper callbacks) {
        return new ConnectionRecord(pkg, pid, uid, callbacks);
    }

    protected Bundle onConnect(@NonNull final Bundle clientData, @NonNull ConnectionRecord connectionRecord) {
        return null;
    }

    protected void onDisconnect(@NonNull final Bundle clientData, @NonNull ConnectionRecord connectionRecord) {
    }

    protected abstract void handleMessageInternal(@NonNull Message msg);

    protected abstract void handleClientMessage(@NonNull Message msg, @NonNull CallbacksWrapper callbacksWrapper);

    private void connect(@NonNull final Bundle clientData, @NonNull final CallbacksWrapper callbacks) {
        mServiceHandler.postOrRun(new Runnable() {
            @Override
            public void run() {
                final String pkg = clientData.getString(DATA_PACKAGE_NAME);
                final int pid = clientData.getInt(DATA_CALLING_PID);
                final int uid = clientData.getInt(DATA_CALLING_UID);
                if (!Utils.isValidPackage(pkg, uid)) {
                    throw new IllegalArgumentException("Package/uid mismatch: " + pkg + "/" + uid);
                }
                final IBinder binder = callbacks.asBinder();
                mConnectionRecords.remove(binder);
                final ConnectionRecord connectionRecord = onSetupConnectionRecord(pkg, pid, uid, callbacks);
                mConnectionRecords.put(binder, connectionRecord);
                try {
                    if (Utils.nonNull(binder)) {
                        binder.linkToDeath(connectionRecord, 0);
                    }
                    callbacks.sendRequest(SERVICE_MSG_ON_CONNECT, onConnect(clientData, connectionRecord));
                } catch (RemoteException e) {
                    MyLog.e("Calling onConnect() failed. Dropping client. pkg=" + pkg, e);
                    mConnectionRecords.remove(binder);
                }
            }
        });
    }

    private void disconnect(@NonNull final Bundle clientData, @NonNull final CallbacksWrapper callbacks) {
        mServiceHandler.postOrRun(new Runnable() {
            @Override
            public void run() {
                final IBinder binder = callbacks.asBinder();
                final ConnectionRecord connectionRecord = mConnectionRecords.remove(binder);
                if (Utils.nonNull(connectionRecord)) {
                    onDisconnect(clientData, connectionRecord);
                    // TODO: 10/14 0014 以下两者是否有区别？
//                        connectionRecord.callbacks.asBinder().unlinkToDeath(connectionRecord,0);
                    if (Utils.nonNull(binder)) {
                        binder.unlinkToDeath(connectionRecord, 0);
                    }
                }
            }
        });
    }

    /**
     * 可以通过遍历该map取出个binder来进行相应的操作
     *
     * @return
     */
    @NonNull
    protected final ArrayMap<IBinder, ConnectionRecord> getConnectionRecords() {
        return mConnectionRecords;
    }

    protected void notifyAllClient(int what, Bundle data) {
        final int N = mConnectionRecords.size();
        if (N > 0) {
            for (ConnectionRecord connectionRecord : mConnectionRecords.values()) {
                if (Utils.nonNull(connectionRecord)) {
                    try {
                        connectionRecord.callbacks.sendRequest(what, data);
                    } catch (RemoteException e) {
                        MyLog.e(e);
                    }
                }
            }
        }
    }

    @NonNull
    protected final ServiceHandler getServiceHandler() {
        return mServiceHandler;
    }

    private BindingToken verifyToken(BindingToken token) {
        return mToken.equals(token) ? token : null;
    }

    public static final class CallbacksWrapper {
        private final Messenger mCallbacks;
        private final BindingToken mToken;

        private CallbacksWrapper(Messenger callbacks, BindingToken token) {
            mCallbacks = callbacks;
            mToken = token;
        }

        private static CallbacksWrapper wrapper(Messenger callbacks, BindingToken token) {
            return new CallbacksWrapper(callbacks, token);
        }

        public IBinder asBinder() {
            if (Utils.nonNull(mCallbacks)) {
                return mCallbacks.getBinder();
            }
            return null;
        }

        public void sendRequest(int what, Bundle data) throws RemoteException {
            if (Utils.nonNull(mCallbacks) && Utils.nonNull(mToken)) {
                Message msg = Message.obtain();
                msg.what = what;
                msg.arg1 = SERVICE_VERSION_CURRENT;
                msg.setData(data);
                msg.getData().putParcelable(BindingProtocol.DATA_TOKEN, mToken);
                mCallbacks.send(msg);
            }
        }
    }

    @SuppressLint("HandlerLeak")
    public final class ServiceHandler extends RunHandler {
        @Override
        public final void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            if (msg.what == CLIENT_MSG_CONNECT) {
                connect(msg.getData(), CallbacksWrapper.wrapper(msg.replyTo, mToken));
            } else if (BindingToken.isBindingToken(msg.getData().getParcelable(BindingProtocol.DATA_TOKEN))) {
                final BindingToken token = verifyToken((BindingToken) msg.getData().getParcelable(BindingProtocol.DATA_TOKEN));
                if (Utils.nonNull(token)) {
                    CallbacksWrapper callbacksWrapper = CallbacksWrapper.wrapper(msg.replyTo, token);
                    if (msg.what == CLIENT_MSG_DISCONNECT) {
                        disconnect(msg.getData(), callbacksWrapper);
                    } else {
                        handleClientMessage(msg, callbacksWrapper);
                    }
                } else {
                    MyLog.e(new IllegalStateException("Token is null!!!"));
                }
            } else {
                handleMessageInternal(msg);
            }
        }

        @Override
        public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
            Bundle data = msg.getData();
            data.putInt(DATA_CALLING_UID, Binder.getCallingUid());
            data.putInt(DATA_CALLING_PID, Binder.getCallingPid());
            return super.sendMessageAtTime(msg, uptimeMillis);
        }
    }

    protected class ConnectionRecord implements IBinder.DeathRecipient {

        public final RemoteUserInfo remoteUserInfo;
        public final CallbacksWrapper callbacks;

        private ConnectionRecord(String pkg, int pid, int uid, @NonNull CallbacksWrapper callback) {
            this.remoteUserInfo = new RemoteUserInfo(pkg, pid, uid);
            this.callbacks = callback;
        }

        @Override
        public void binderDied() {
            mServiceHandler.post(new Runnable() {
                @Override
                public void run() {
                    mConnectionRecords.remove(callbacks.asBinder());
                }
            });
        }
    }

}
